#include "StdAfx.h"
#include <MyControls/ListControl.h>

#include <algorithm>
#include <functional>

namespace My {

// ListControl

BEGIN_MESSAGE_MAP(ListControl, ListControl::Base)
	ON_WM_VSCROLL()
	ON_WM_CREATE()
	ON_WM_DESTROY()
	ON_WM_ERASEBKGND()
END_MESSAGE_MAP()

ListControl::ListControl()
{
	m_Invalid = 0;
	m_First = 0;
	m_Last = 0;

	m_Range = 0;
	m_Page = 0;
	m_Offset = 0;

	m_ActiveRow = -1;

	m_Style = WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_CLIPCHILDREN;
	m_Class = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS, /*::LoadCursor(NULL, IDC_ARROW)*/0, 0, 0);
	m_StyleEx = 0;

	UpdatePage();
}

ListControl::~ListControl()
{
}
	   
int ListControl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{			   
	if (Base::OnCreate(lpCreateStruct) == -1)
		return -1;

	InitializeFlatSB(m_hWnd);

	FlatSB_SetScrollProp(m_hWnd, WSB_PROP_CXVSCROLL, 12, FALSE);
	FlatSB_SetScrollProp(m_hWnd, WSB_PROP_CYVSCROLL, 12, FALSE);
	FlatSB_SetScrollProp(m_hWnd, WSB_PROP_VSTYLE, FSB_REGULAR_MODE, FALSE);

	UpdateScrollBar();

	return 0;
}

void ListControl::OnDestroy()
{
	UninitializeFlatSB(m_hWnd);
	Base::OnDestroy();
}

BOOL ListControl::OnEraseBkgnd(CDC *)
{
	return TRUE;
}

void ListControl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar )
{
	int y = m_Offset;

	switch (nSBCode) {
	case SB_LEFT:
		y = 0;
		break;
	case SB_ENDSCROLL:
		break;
	case SB_LINELEFT:
		y -= 10;
		break;
	case SB_LINERIGHT:
		y += 10;
		break;
	case SB_PAGELEFT:
		y -= m_Page;
		break;
	case SB_PAGERIGHT:
		y += m_Page;
		break;
	case SB_RIGHT:
		y = m_Range - m_Page;
		break;
	case SB_THUMBPOSITION:
	case SB_THUMBTRACK:
		y = nPos;
		break;
	default:
		return;
	}
	SetOffset(y);
}

void ListControl::SetOffset(int offset)
{
	if (IsEmpty())
		return;
	if (offset > m_Range - m_Page)
		offset = m_Range - m_Page;
	if (offset < 0)
		offset = 0;
	if (offset != m_Offset)
	{
		m_Offset = offset;

		for (unsigned int i = 0; i < m_Rows.size(); ++i)
			m_Rows[i]->UpdateBounds();

		UpdateScrollBar();
		Invalidate();
	}
}

void ListControl::UpdatePage()
{
	m_Page = Height();

	UpdateOffset();
	UpdateScrollBar();
}

void ListControl::UpdateScrollBar()
{
	SCROLLINFO info;
	info.cbSize = sizeof(SCROLLINFO);     
	info.fMask = SIF_ALL|SIF_DISABLENOSCROLL;     
	info.nMin = 0;     
	info.nMax = m_Range; 
	info.nPage = m_Page;
	info.nPos = m_Offset;
	info.nTrackPos = m_Offset;
	if (m_Range == m_Page)
		info.nPage++;
//	SetScrollInfo(SB_VERT, &info);
	FlatSB_SetScrollInfo(m_hWnd, SB_VERT, &info, TRUE);
}

void ListControl::UpdateBounds()
{
	Base::UpdateBounds();

	for (unsigned int i = 0; i < m_Rows.size(); ++i)
		m_Rows[i]->UpdateBounds();

	UpdatePage();
}

void ListControl::InvalidateRows(My::Size from)
{
	if (from < 0)
		from = 0;
	if (from > int(m_Rows.size()))
		from = m_Rows.size();
	if (from < m_Invalid) {
		m_Invalid = from;
		//if (m_Invalid < m_Last)
	}
	Invalidate();
}

void ListControl::UpdateRows()
{
	if (m_Rows.empty())
		return;
	
	if (m_Invalid < int(m_Rows.size())) {

		int y = 0;
		if (m_Invalid > 0) {
			RowControl * pValid = m_Rows[m_Invalid - 1];
			y = pValid->m_RowOffset + pValid->m_RowHeight;
		}

		for (int i = m_Invalid; i < int(m_Rows.size()); ++i) {
			m_Rows[i]->m_RowOffset = y;
			y += m_Rows[i]->m_RowHeight;
			m_Rows[i]->UpdateBounds();
			m_Rows[i]->ProcessEvent(UpdateUIEvent());
		}

		m_Range = y;
		m_Invalid = m_Rows.size();

		UpdatePage();
		UpdateOffset();
	}
}

struct RowIsAbove : std::binary_function<RowControl *, int, bool> {
	bool operator () (RowControl * pRow, int y) const {
		return pRow->GetRowOffset() + pRow->GetRowHeight() < y;
	}
};

int ListControl::RowIndexAt(int y)
{
	RowVector::iterator it = std::lower_bound(m_Rows.begin(), m_Rows.end(), 
		y + m_Offset, RowIsAbove());
	if (it != m_Rows.end())
		return (* it)->m_RowIndex;
	else
		return -1;
}

void ListControl::ClampInView(My::Size index)
{
	if (index >= GetRowCount())
		return;
	int y = m_Rows[index]->m_RowOffset;
	int h = m_Rows[index]->m_RowHeight;
	if (y + h > m_Offset + m_Page)
		SetOffset(y + h - m_Page);
	if (y < m_Offset)
		SetOffset(y);
}

void ListControl::InsertRows(My::Size pos, RowControl * * pRows, My::Size count)
{
	m_Rows.insert(m_Rows.begin() + pos, pRows, pRows + count);
	if (m_Invalid >= pos)
		m_Invalid += count;
	My::Size i;
	for (i = pos; i < pos + count; ++i) {
		Add(m_Rows[i]);
		m_Rows[i]->m_RowIndex = i;
	}
	for (i = pos + count; i < m_Rows.size(); ++i) {
		m_Rows[i]->m_RowIndex = i;
	}
	OnRowsChanged();
	InvalidateRows(pos);
}

void ListControl::RemoveRows(My::Size pos, My::Size count)
{
	std::for_each(m_Rows.begin() + pos, m_Rows.begin() + pos + count, 
		std::bind1st(std::mem_fun(Remove), this));
	m_Rows.erase(m_Rows.begin() + pos, m_Rows.begin() + pos + count);
	for (My::Size i = pos; i < m_Rows.size(); ++i) {
		m_Rows[i]->m_RowIndex = i;
	}
	OnRowsChanged();
	InvalidateRows(pos);
}

void ListControl::Paint(CDC * pDC, const CRect & r)
{
	My::ClipRect clip(this, pDC, ClientRect());

	UpdateRows();
	My::Container::Paint(pDC, r);
}

void ListControl::Clear()
{
	RemoveRows(0, GetRowCount());
}

void ListControl::SetValidActiveRow(int index)
{
	if (index < 0)
		index = 0;
	SetActiveRow(index);
}

void ListControl::SetActiveRow(int index)
{
	if (index != -1) {
		if (index < 0)
			index = 0;
		if (index > int(GetRowCount()) - 1)
			index =  int(GetRowCount()) - 1;
	}
	if (index != m_ActiveRow) {
		if (m_ActiveRow != -1)
			GetRow(My::Size(m_ActiveRow))->Invalidate();
		m_ActiveRow = index;
		if (m_ActiveRow != -1) {
			ClampInView(m_ActiveRow);
			GetRow(My::Size(m_ActiveRow))->Invalidate();
		}
	}
}

void ListControl::HandleLeftDown(unsigned int flags, const CPoint & p)
{
	SetActiveRow(-1);
	Base::HandleLeftDown(flags, p);
}

void ListControl::HandleFocus()
{
	if (m_ActiveRow != -1)
		GetRow(m_ActiveRow)->Invalidate();
}

void ListControl::HandleUnfocus()
{
	if (m_ActiveRow != -1)
		GetRow(m_ActiveRow)->Invalidate();
}

void ListControl::HandleKeyDown(unsigned int key, unsigned int count, unsigned int flags)
{
	if (m_ActiveRow == -1)
		return;

	switch (key) {
	case VK_UP:
		SetValidActiveRow(m_ActiveRow - 1);
		break;
	case VK_DOWN:
		SetValidActiveRow(m_ActiveRow + 1);
		break;
	case VK_PRIOR: {
		int offset = GetRow(m_ActiveRow)->GetRowOffset() - GetOffset();
		int index = RowIndexAt(offset - Height());
		SetValidActiveRow(index);
		break;
	}
	case VK_NEXT: {
		int offset = GetRow(m_ActiveRow)->GetRowOffset() - GetOffset();
		int index = RowIndexAt(offset + Height());
		if (index == -1)
			index = GetRowCount() - 1;
		SetValidActiveRow(index);
		break;
	}
	case VK_HOME:
		SetValidActiveRow(0);
		break;
	case VK_END:
		SetValidActiveRow(GetRowCount() - 1);
		break;
	default:
		GetActiveRowPtr()->HandleKeyDown(key, count, flags);
	}
}

// RowControl

RowControl::RowControl(ListControl & owner)
	: m_Owner(owner)
{
	m_RowIndex = -1;
	m_RowHeight = 0;
	m_RowOffset = 0;
}

RowControl::~RowControl()
{
}

void RowControl::SetRowHeight(int h)
{
	if (h != m_RowHeight) {
		m_RowHeight = h;
		GetOwner().InvalidateRows(m_RowIndex + 1);
	}
}

CRect RowControl::Bounds() const
{
	CRect r = GetOwner().ClientRect();
	r.top = m_RowOffset - GetOwner().m_Offset;
	r.bottom = r.top + m_RowHeight;
	return r;
}

// StdRowControl

StdRowControl::StdRowControl(ListControl & owner) :
	RowControl(owner)
{
	m_Indent = 16;
}

void StdRowControl::HandlePaint(CDC * pDC, const CRect & r)
{
	My::SaveObjects save(pDC);

	CRect rClient = ClientRect();

	COLORREF cS	= GetSysColor(COLOR_BTNSHADOW);
	COLORREF cF	= GetSysColor(COLOR_BTNFACE);
	COLORREF cLF = My::MeanSysColor(COLOR_BTNFACE, COLOR_BTNHILIGHT);
	COLORREF cL = GetSysColor(COLOR_BTNHILIGHT);
	COLORREF cViolet = RGB(224, 224, 255);

	pDC->SetBkMode(TRANSPARENT);

	CBrush bGround;
	if (GetRowIndex() != GetOwner().GetActiveRow())
		bGround.CreateSolidBrush(cLF);
	else
		bGround.CreateSolidBrush(cViolet);
	pDC->FillRect(&rClient, &bGround);

	CRect rCaption(rClient);

	My::Glyph btnUp; btnUp.Load("LISTBUTTONUP", "LISTBUTTONUPMASK");
	My::Glyph btnDown; btnDown.Load("LISTBUTTONDOWN", "LISTBUTTONDOWNMASK");

	rCaption.left =	btnUp.Width() + GetLevel() * m_Indent;

	if (!rCaption.IsRectEmpty()) {

		CRect rText(rCaption);
		rText.InflateRect(-4, 0);

		String caption = GetCaption();
		pDC->DrawText(caption.c_str(), &rText, DT_SINGLELINE|DT_END_ELLIPSIS|DT_LEFT);
		pDC->DrawText(caption.c_str(), &rText, DT_SINGLELINE|DT_END_ELLIPSIS|DT_CALCRECT);

		rText = My::AlignRect(rCaption, rText, ALIGN_VTOP);
		rText.InflateRect(2, 1);

		if (GetRowIndex() == GetOwner().GetActiveRow() && GetOwner().HasFocus())
			pDC->DrawFocusRect(&rText);

		if (IsExpandable())	{

			m_ButtonRect = rText;
			m_ButtonRect.right	= rCaption.left;
			m_ButtonRect.left	= m_ButtonRect.right - btnUp.Width() + 2;

			if (IsExpanded())
				btnDown.Draw(pDC, m_ButtonRect);
			else
				btnUp.Draw(pDC, m_ButtonRect);
		}
	}
}

void StdRowControl::HandleLeftDoubleClick(unsigned int flags, const CPoint & p)
{
	RowControl::HandleLeftDoubleClick(flags, p);
	if(IsExpandable()) {
		if(IsExpanded())
			Collapse();
		else
			Expand();
	}
}

void StdRowControl::HandleLeftDown(unsigned int flags, const CPoint & p)
{
	RowControl::HandleLeftDown(flags, p);
	if (IsExpandable() && m_ButtonRect.PtInRect(p)) {
		if(IsExpanded())
			Collapse();
		else
			Expand();
	} else {
		GetOwner().SetActiveRow(GetRowIndex());
	}
}

} // My